package com.qn.utils;

import com.qn.entity.Ipconfig;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @since 2021/5/25 19:37   @author 黄志源大魔王  @version 1.00
 * Description 爬取66免费代理网所有IP
 */
public class IPUtils {
    /**
     * 爬取该网站所有免费提供的代理IP 并将IP存入到文件中
     *
     * @return 返回IP集合
     * @throws Exception
     */
    public static List crawl() throws Exception {
        Integer index = 0;//每个线程爬取的起始页
        Integer index2 = 50;//每个爬取的结束页
        //这个集合就是我们所要返回的对象了
        ArrayList ipConfigList = new ArrayList<Ipconfig>();
        //这个是干嘛的？偶偶，这个就是用来搞一下随机线程睡眠时间，不设置也无所谓
        Random random = new Random();
        /*创建线程池*/
        PoolSend poolSend = new PoolSend(46, 46);
        /*使用for循环开启46个线程，每个线程爬取50个页面，一共爬取2300页面的IP*/
        for (int i = 0; i < 46; i++) {
            Integer finalIndex = index;
            Integer finalIndex1 = index2;
            //使用线程池
            poolSend.send(() -> {
                /*要返回的对象*/
                for (int j = finalIndex; j <= finalIndex1; j++) {
                    try {
                        URL url = null;
                        if (j == 1) { //是第一页的时候
                            url = new URL("http://www.66ip.cn/index.html");
                        } else {
                            url = new URL("http://www.66ip.cn/" + j + ".html");
                        }
                        //随机一个休眠时间
                        Long s = (long) (random.nextInt(5000 - 1000 + 1) + 1000);
                        //当前线程休眠，休眠过后就开始解析网页
                        Thread.currentThread().sleep(s);
                        /*传入爬取的url和超时等待时间*/
                        Document document = Jsoup.parse(url, 1000000);
                        //通过css选择器来获取对应的标签
                        Elements table = document.select("table tr:not(tr:nth-child(1))");
                        //遍历获取IP地址
                        for (Element element : table) {
                            //ip
                            String ip = element.child(0).text();
                            //端口
                            String port = element.child(1).text();
                            //然后将ip和port封装成我们前面的那个对象
                            Ipconfig ipconfig = new Ipconfig(ip, port);
                            /**
                             * isValid？啊你tm这个方法是个啥？
                             * 这个方法是用来判断该ip是否可用的，可以看下面文章，有给出代码
                             * 返回true就表示这个ip得行，放心用
                             * 返回false的话。。。懂得都懂
                             */
                            boolean valid = isValid(ipconfig);
                            if (valid) {
                                //可以用的ip就放入我们要返回的对象中
                                ipConfigList.add(ipconfig);
                                System.out.println("====爬取成功" + ipConfigList.size());
                            } else {
                                System.out.println("很遗憾，这个ip不合格:" + ipconfig.getIp() + "\t port:" + ipconfig.getPort());
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("====爬取成功：一共爬取了" + ipConfigList.size() + "可用ip");
                    /*爬取1000条ip后停止，这段代码看个人吧，我这边不想爬取太多的ip*/
                    if (ipConfigList.size() > 1000) {
                        break;
                    }
                }
            });
            index += 50;
            index2 += 50;
        }
        /*关闭线程*/
        poolSend.close();
        System.out.println("执行玩球了");
        /*输出我们爬取成功可用的ip集合*/
        for (Object o : ipConfigList) {
            System.out.println(o);
        }

        /**
         * 这个方法又是干球的？
         * 咳咳，这个方法是用来将这个对象存入到某个文件中的，
         * 参数就是存入要存的对象
         */
        accessObject(ipConfigList);
        return ipConfigList;
    }

    /**
     * 爬取该网站中所有的国内IP
     *
     * @throws Exception
     */
    public static ArrayList<Ipconfig> crawl2() throws Exception {
        ArrayList ipConfigList = new ArrayList<Ipconfig>();
        Random random = new Random();
        /*创建一个线程池*/
        PoolSend poolSend = new PoolSend(34, 34);
        /*创建34个线程执行爬虫，每个线程爬一个页面*/
        for (int i = 1; i <= 34; i++) {
            int finalI = i;
            poolSend.send(() -> {
                try {
                    URL url = null;
                    url = new URL("http://www.66ip.cn/areaindex_" + finalI + "/1.html");
                    //当前线程休眠
                    Long s = (long) (random.nextInt(5000 - 1000 + 1) + 1000);
                    Thread.currentThread().sleep(s);
                    /*传入爬取的url和超时等待时间*/
                    Document document = Jsoup.parse(url, 1000000);
                    //遍历获取IP地址
                    Elements table = document.select("table tr:not(tr:nth-child(1))");
                    for (Element element : table) {
                        //ip
                        String ip = element.child(0).text();
                        //端口
                        String port = element.child(1).text();
                        Ipconfig ipconfig = new Ipconfig(ip, port);
                        boolean valid = isValid(ipconfig);
                        if (valid) {
                            ipConfigList.add(ipconfig);
                            System.out.println("====爬取成功" + ipConfigList.size());
                        } else {
                            System.out.println("很遗憾，这个ip不合格:" + ipconfig.getIp() + "\t port:" + ipconfig.getPort());
                        }
                    }
                    System.out.println(url.toString() + "爬取完毕");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        poolSend.close();
        //将对象存入到文件中
        accessObject(ipConfigList);
        return ipConfigList;
    }


    public static void accessObject(Object obj) throws Exception {
        /*将这个对象存入到文件中*/
        //1、创建ObjectOutputStream对象，构造方法中传递字节输出流
        System.out.println("开始存入");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj\\ipconfig.txt"));
        //2、使用ObjectOutputStream对象中的方法writeObject，把对象写入文件中
        oos.writeObject(obj);
        //3、释放资源
        oos.close();
    }


    /**
     * 读取文件对象
     *
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static ArrayList<Ipconfig> duqvObject() throws IOException, ClassNotFoundException {
        //1、创建ObjectInputStream对象，构造方法中传递字节输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj//ipconfig.txt"));
        //2、使用ObjectInputStream对象中的方法readObject读取保存对象的文件
        Object o = ois.readObject();
        //3、释放资源
        ois.close();
        //4、使用读取出来对的对象（打印）
        System.out.println(o);
        ArrayList<Ipconfig> p = (ArrayList<Ipconfig>) o;//转换
        for (Ipconfig ipconfig : p) {
            System.out.println(ipconfig);
        }
        return p;
    }

    /**
     * 检测代理ip是否有效
     *
     * @param ipBean
     * @return
     */
    public static boolean isValid(Ipconfig ipBean) {
        /*设置代理*/
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ipBean.getIp(), Integer.parseInt(ipBean.getPort())));
        try {
            /*访问百度*/
            URLConnection httpCon = new URL("https://www.baidu.com/").openConnection(proxy);
            httpCon.setConnectTimeout(2000);//连接超时
            httpCon.setReadTimeout(2000);//读取超时如果不设置，它就会一直阻塞着。
            int code = ((HttpURLConnection) httpCon).getResponseCode();
            System.out.println(code);
            return code == 200;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) throws Exception {
        crawl2();
        duqvObject();
    }
}